#pragma once
#include <sstream>
#include <iostream>
#include <iomanip>
#include <map>
#include <stack>
#include <atomic>

#include "vtime.h"
#include "unique_instance.h"

#ifdef _VLOG_DISABLE_  //forbiden vlog
#define _VLOG_(LEVEL) vlog_tmp_disable()  //create a tmp object
#else
#define _VLOG_(LEVEL) vlog_tmp(LEVEL,__FILE__,__LINE__,__FUNCTION__).get_vlog()
#endif

//user vlog interface
#define vlogt _VLOG_(nvlog::trace) 
#define vlogd _VLOG_(nvlog::debug) 
#define vlogl _VLOG_(nvlog::detail) 
#define vlogi _VLOG_(nvlog::info)
#define vlogw _VLOG_(nvlog::warn)
#define vloge _VLOG_(nvlog::error)
#define vlogc _VLOG_(nvlog::critical)

//for none multiple thread support
#define vlogs std::cout

namespace nvlog
{
    enum level
    {
        trace,    //0
        debug,    //1   
        detail,   //2     
        info,     //3
        warn,     //4
        error,    //5
        critical, //6 
        audit     //7
    };
}

//used for support multiple thread
class vlog_str
{
    public:
        vlog_str();
        ~vlog_str();

    public:
        virtual bool should_log() const;

        virtual void set_header(const std::string & file, 
                int line, 
                const std::string & function);

        virtual void set_level(nvlog::level l); //current log out level
        virtual void set_seting_level(nvlog::level l); //user seting level
        virtual void log_out() const;

        const std::string & str() const;
        std::string & str();

        static std::string get_level_str(nvlog::level _level_);

    protected:
        std::string _file; 
        int _line=0; 
        std::string _function;
        nvlog::level _level=nvlog::debug;
        nvlog::level _user_settng_level=nvlog::debug;

        std::string _header;
        std::string _content;
};

//used for support multiple thread
class vlog_thread
{
    public:
        vlog_thread();
        ~vlog_thread();

    public:
        vlog_str & push();
        const vlog_str & top() const;
        vlog_str & top();
        void pop();

        bool empty() const;
        size_t size() const;

        std::string str() const;

    protected:
        std::string _str;
        std::stack<vlog_str> _stack_vlog_str;
};

class vlog_tmp;
class vlog
{
    public:
        friend class vlog_tmp;
        UNIQUE_INSTANCE(vlog);

    protected:
        using std_endl_t = std::ostream& (*)(std::ostream&);

    protected:
        vlog() noexcept;
        vlog(const vlog&)=delete;
        virtual ~vlog();

    public:
        //used for std::endl template function,
        //now can be used like vlogi<<std::endl;
        void operator<< (std_endl_t pf);

        template <typename T>
            vlog & operator<<(const T & data)
            {
                auto & vth = get_vlog_thread();
                auto & vstr = vth.top();
                if(vstr.should_log())
                {
                    _oss.clear(); _oss.str("");_oss<<data;
                    std::string str_tmp = _oss.str();
                    std::cout<<str_tmp;
                    vstr.str()+=std::move(str_tmp);
                }
                return *this;
            }

    public:
        virtual void set_level(nvlog::level l); //user want log out level
        virtual vlog & log_out(nvlog::level level_cur,
                const std::string &file, 
                int line, 
                const std::string &function,
                std::unique_lock<std::mutex> & unq_lock);

        virtual vlog & log_out(nvlog::level level_cur,
                const std::string & file, 
                int line, 
                const std::string & function){return *this;}

        virtual std::string str() const;
        virtual std::string information();

        std::mutex & get_mutex() const;

    protected:
        bool is_current_thread_locked() const; 
        void set_current_thread_locked() ;
        void set_current_thread_unlocked() ;

        virtual vlog_thread & get_vlog_thread();
        const vlog_thread & get_vlog_thread() const;

        virtual bool should_log() const;
        //virtual bool should_log(nvlog::level user_level) const;

    protected:
        std::string _file; 
        int _line=0; 
        std::string _function;
        nvlog::level _level=nvlog::debug;
        nvlog::level _user_want_level=nvlog::debug;

        std::ostringstream _oss;

        //std::map<std::thread::id, vlog_thread> _map_tid_vlogth;
        static thread_local vlog_thread _vlog_thread;

        std::atomic<std::thread::id> _current_locked_tid;
        std::unique_lock<std::mutex> * _punq_lock=nullptr;        

        mutable std::mutex _mutex_vlog;

        const std_endl_t _pf_endl = std::endl;
};

//use RAII to manager lock
class vlog_tmp
{
    public:
        vlog_tmp(nvlog::level level_cur,
                const std::string & file, 
                int line, 
                const std::string & function);

        virtual ~vlog_tmp();

    protected:
        vlog_tmp()=delete;
        vlog_tmp(const vlog_tmp & v)=delete;
        vlog_tmp& operator=(const vlog_tmp & v)=delete;

    public:
        vlog & get_vlog();

    protected:
        std::string _file; 
        int _line=0; 
        std::string _function;
        nvlog::level _level=nvlog::debug;

        std::unique_lock<std::mutex> _lock;
};

class vlog_tmp_disable
{
    public:
        vlog_tmp_disable(){};
        ~vlog_tmp_disable(){};

    public:
        template <typename T>
            const vlog_tmp_disable & operator<<(const T & data) const
            {
                return *this;
            }

        void operator<< (std::ostream& (*pf)(std::ostream&)) const{}

};
//} const _global_vlog_tmp_disable; //it is better not use global variable


